//
//  PSRostovCalculator.m
//  PrefCore
//
//  Created by Sergey on 2/27/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import "PSRostovCalculator.h"

#define HELP_FACTOR 10

@interface PSRostovCalculator ()

-(NSArray*)sortedOpponentsForHelper:(PSPlayer*)helper
                               game:(PSGame*)game
                           settings:(PSGameSettings*)settings;

-(void)addToPoolOfPlayer:(PSPlayer*)player
                    game:(PSGame*)game
                settings:(PSGameSettings*)settings
                   value:(PSScoreValue)value;

-(void)reduceFromMountainOfPlayer:(PSPlayer*)player
                             game:(PSGame*)game
                         settings:(PSGameSettings*)settings
                            value:(PSScoreValue)value
                           punish:(BOOL)punishByAddingToMountain;

-(void)completeHelp:(PSScoreValue)help
               game:(PSGame*)game
           settings:(PSGameSettings*)settings
             player:(PSPlayer*)player
             punish:(BOOL)punishByAddingToMountain;

@end

@implementation PSRostovCalculator

-(instancetype)init {
    if (self = [super initWithPassingScorer:[PSScorer passingRostovScorer]
                           costsPlaySuccess:@[@2, @4,  @6,  @8, @10]
                              costsPlayFail:@[@2, @4,  @6,  @8, @10]
                          costsWhistSuccess:@[@2, @4,  @6,  @8, @10]
                             costsWhistFail:@[@1, @2,  @3,  @4, @5]
                        normalizationFactor:2]) {
    }
    return self;
}

-(PSScoreValue)missedTrickCostWithTricks:(PSTrickCount)numTricks {
    return HELP_FACTOR;
}

-(BOOL)isClosed:(PSPlayer*)p game:(PSGame*)game settings:(PSGameSettings*)settings {
    return [super isClosed:p game:game settings:settings]
        && [game scoreForPlayerId:p.identifier].mountain.current == 0;
}

-(void)writeToPoolOfPlayer:(PSPlayer*)player
                      game:(PSGame*)game
                  settings:(PSGameSettings*)settings
                     value:(PSScoreValue)value {
    BOOL poolsAreClosed = TRUE;
    for (PSPlayer *p in game.players) {
        poolsAreClosed &= [super isClosed:p game:game settings:settings];
        if (!poolsAreClosed) {
            break;
        }
    }
    // all players have closed their pools
    if (poolsAreClosed) {
        // reduce from mountain and "help" to opponents if needed (without "podlyot")
        [self reduceFromMountainOfPlayer:player game:game settings:settings value:value punish:NO];
    } else {
        // get half cost of the declared game or passing
        PSScoreValue half = value / 2;
        // add to pool and "help" if needed (with "podlyot")
        [self addToPoolOfPlayer:player game:game settings:settings value:half];
        // reduce from mountain and "help" to opponents if needed (with "podlyot")
        [self reduceFromMountainOfPlayer:player game:game settings:settings value:half punish:YES];
    }
}

-(void)addToPoolOfPlayer:(PSPlayer*)player
                    game:(PSGame*)game
                settings:(PSGameSettings*)settings
                   value:(PSScoreValue)value {
    PSScorePool *pool = [game scoreForPlayerId:player.identifier].pool;
    if (pool.current + value <= settings.poolSize) {
        [pool addValue:value];
    } else {
        // get remaining amount for "help"
        PSScoreValue help = pool.current + value - settings.poolSize;

        // close the pool
        if (pool.current != settings.poolSize) {
            [pool addValue:settings.poolSize - pool.current];
        }
        // get opponents sorted by Rostov's "help" rules
        for (PSPlayer *op in [self sortedOpponentsForHelper:player game:game settings:settings]) {
            // "help" the specific opponent to close his pool
            PSScorePool *opPool = [game scoreForPlayerId:op.identifier].pool;
            PSScoreValue min = MIN(help, settings.poolSize - opPool.current);
            
            if (min > 0) {
                [opPool addValue:min];
                [self writeWhistsFromPlayer:player toPlayer:op game:game value:min * HELP_FACTOR];
                help -= min;
            }
        }
        // the player still has some amount to "help" the opponents
        if (help > 0) {
            [self completeHelp:help game:game settings:settings player:player punish:YES];
        }
    }
}

-(void)reduceFromMountainOfPlayer:(PSPlayer*)player
                             game:(PSGame*)game
                         settings:(PSGameSettings*)settings
                            value:(PSScoreValue)value
                           punish:(BOOL)punishByAddingToMountain {
    PSClosableValues *mountain = [game scoreForPlayerId:player.identifier].mountain;
    if (mountain.current >= value) {
        [mountain addValue:-value];
    } else {
        // get remaining amount for "help"
        PSScoreValue help = value - mountain.current;
        // eliminate mountain
        if (mountain.current > 0) {
            [mountain addValue:-mountain.current];
        }
        // "help" the opponents
        for (PSPlayer *op in [self sortedOpponentsForHelper:player game:game settings:settings]) {
            // "help" the specific opponent to eliminate his mountain
            PSClosableValues *opMountain = [game scoreForPlayerId:op.identifier].mountain;
            PSScoreValue min = MIN(help, opMountain.current);
            if (min > 0) {
                [opMountain addValue:-min];
                [self writeWhistsFromPlayer:player toPlayer:op game:game value:HELP_FACTOR * min];
                help -= min;
            }
        }
        // the player still has some amount to "help" the opponents
        if (help > 0) {
            [self completeHelp:help game:game settings:settings player:player punish:punishByAddingToMountain];
        }
    }
}


-(void)completeHelp:(PSScoreValue)help
               game:(PSGame*)game
           settings:(PSGameSettings*)settings
             player:(PSPlayer*)player
             punish:(BOOL)punishByAddingToMountain {
    PSPlayer *p2 = [game nextPlayer:player];
    BOOL closed2 = [self isClosed:p2 game:game settings:settings];
    PSPlayer *p3 = [game nextPlayer:p2];
    BOOL closed3 = [self isClosed:p3 game:game settings:settings];
    PSScoreValue hw = HELP_FACTOR / 2 * help;

    // first check if opponents are totally closed, if true then just write whists on them
    if (closed2 && closed3) {
        [self writeWhistsFromPlayer:player toPlayer:p2 game:game value:hw];
        [self writeWhistsFromPlayer:player toPlayer:p3 game:game value:hw];
    } else {
        // "podlyot"
        if (punishByAddingToMountain) {
            if (closed2) {
                [self writeWhistsFromPlayer:player toPlayer:p2 game:game value:hw];
            } else {
                [self writeToMountainOfPlayer:p2 game:game value:help];
            }
            if (closed3) {
                [self writeWhistsFromPlayer:player toPlayer:p3 game:game value:hw];
            } else {
                [self writeToMountainOfPlayer:p3 game:game value:help];
            }
        } else {
            // "help" to each opponent by reducing his mountain and writing whists against him
            if ([game scoreForPlayerId:p2.identifier].mountain.current > 0) {
                PSScoreValue min = MIN([game scoreForPlayerId:p2.identifier].mountain.current, help);
                [self writeToMountainOfPlayer:p2 game:game value:-min];
                [self writeWhistsFromPlayer:player toPlayer:p2 game:game value:HELP_FACTOR * min];
                help -= min;
            }
            if (help > 0 && [game scoreForPlayerId:p3.identifier].mountain.current > 0) {
                PSScoreValue min = MIN([game scoreForPlayerId:p3.identifier].mountain.current, help);
                [self writeToMountainOfPlayer:p3 game:game value:-min];
                [self writeWhistsFromPlayer:player toPlayer:p3 game:game value:HELP_FACTOR * min];
                help -= min;
            }
            // the player still has some amount to "help" the opponents => write whists on both opponents
            if (help > 0) {
                hw = HELP_FACTOR / 2 * help;
                [self writeWhistsFromPlayer:player toPlayer:p2 game:game value:hw];
                [self writeWhistsFromPlayer:player toPlayer:p3 game:game value:hw];
            }
        }
    }
}

-(NSArray*)sortedOpponentsForHelper:(PSPlayer*)helper
                               game:(PSGame*)game
                           settings:(PSGameSettings*)settings {
    PSPlayer *p2 = [game nextPlayer:helper];
    PSPlayer *p3 = [game nextPlayer:p2];
    int p2c = [self isClosed:p2 game:game settings:settings] ? 1 : 0;
    int p3c = [self isClosed:p3 game:game settings:settings] ? 1 : 0;
    switch (p2c<<1 | p3c) {
        case 0: return @[];
        case 1: return @[p2];
        case 2: return @[p3];
    }
    // If we get to here, it's case 3 - both players are closed
    PSScore *p2score = [game scoreForPlayerId:p2.identifier];
    PSScore *p3score = [game scoreForPlayerId:p3.identifier];
    PSScoreValue p2pool = p2score.pool.current;
    PSScoreValue p3pool = p3score.pool.current;
    PSScoreValue p2mount = p2score.mountain.current;
    PSScoreValue p3mount = p3score.mountain.current;
    return (p2pool > p3pool || (p2pool == p3pool && p2mount <= p3mount)) ? @[p2, p3] : @[p3, p2];
}

@end
